package oop.ex2.expression;

import java.util.ArrayList;

import oop.ex2.exceptions.EmptyExpression;
import oop.ex2.exceptions.ExpectedAtomicExpression;
import oop.ex2.exceptions.ExpectedBinaryOperation;
import oop.ex2.exceptions.TerminatingStringNotFound;
import oop.ex2.exceptions.TokenNotRecognized;
import oop.ex2.exceptions.UnrecognizedVariable;
import oop.ex2.tokenizer.Tokenizer;

/**
 * This Node builds it's children by adding nodes one by one until it reaches some terminating string.
 * If no expression was found then an exception is thrown.
 * After running, the tokenizer should be after the terminating string.
 * 
 * This node builds it's children in the following fashion. First it looks for an atomic
 * expression - i.e an expression that can be directly evaluated into a number such as a function,
 * constant, or parenthesis - and adds it as a child. 
 * After that, it only looks for a binary operators. Whenever it finds one, it puts it's current
 * child in the left part of the binary operator and replaces it's old child with the binary operator.
 * 
 * For example, if the input is 1 + [2 + 3]
 * Then first 1 will be added as a child, then 1 will be added as a child to + and + will replace
 * 1 as a child. + will then add [2 + 3] as a right side child.
 * 
 * @author daniel_i_l
 *
 */
public class SequenceExpressionNode extends ExpressionNode {
	private final static NodeType NODE_TYPE = NodeType.ATOM;
	private final static String EMPTY_STRING = "";
	
	private ArrayList<String> _terminatingStrings; //build node will terminate when it reaches a node that matches one of these strings
	private boolean _validateTermination;
	private String _terminatedString; //the string that was used to terminate
	
	/**
	 * Constructor that sets the matched string to the empty string.
	 * @param validationTermination true if the node validates termination.
	 */
	public SequenceExpressionNode(boolean validationTermination) {
		 this(EMPTY_STRING, validationTermination);
		}

	/**
	 * Constructor
	 * @param matchedString the matched string.
	 * @param validationTermination true if the node validates termination.
	 */
    public SequenceExpressionNode(String matchedString, boolean validationTermination) {
		super(matchedString);
		_nodeType = NODE_TYPE;
		_terminatingStrings = new ArrayList<String>();
		_validateTermination = validationTermination; //set the validation to true
	}

	@Override
	public void generateChildren(Tokenizer tokenizer) throws TokenNotRecognized, EmptyExpression, 
	                                                   ExpectedAtomicExpression, ExpectedBinaryOperation, TerminatingStringNotFound {
		ExpressionNode currentNode;
		
		if (tokenizer.isEmpty())
			throw new EmptyExpression("A sequence cannot be empty");

		//the first child must be an atom
		tokenizer.advance(NodeType.ATOM);
		
		currentNode = tokenizer.getExpressionNode();

		//if the expression is empty
		if (isTerminating(currentNode.getMatchedString()))
			throw new EmptyExpression("An expression was excpected before the terminating string");

		
	    addChild(currentNode);
		currentNode.generateChildren(tokenizer);

		//if we got up to here, just keep adding binary operators as long as the expression isn't empty
		while (!tokenizer.isEmpty())
		{
			tokenizer.advance();
			currentNode = tokenizer.getExpressionNode();
			
			 if (isTerminating(currentNode.getMatchedString()))
				 break;
			 
			if (! currentNode.getType().equals(NodeType.BINARY_OP))
				throw new ExpectedBinaryOperation("Two atomic operations encountered not seperated by a binary op");

			currentNode.addChild(_children.get(0)); //add current child as a child of the binary operator
			_children.set(0, currentNode); //and set the binary operator as the child
			currentNode.generateChildren(tokenizer);
		}
		
	    if ((_validateTermination) && !(isTerminating(currentNode.getMatchedString())))
			throw new TerminatingStringNotFound("Couldn't find a terminating string");
	}

	@Override
	public double calculateNode() throws UnrecognizedVariable {
		//return the value of the only child
		return _children.get(0).calculateNode();
	}
	
	/**
	 * Determines if the tokenizer validates termination. If so then it throws an exception
	 * if the terminating string wasn't found.
	 * @param validateTermination
	 */
	public void setTerminateValidation(boolean validateTermination)
	{
		_validateTermination = validateTermination;
	}
	
	/**
	 * Add a string to the list of terminating strings.
	 * @param terminatingString The string to add.
	 */
	public void addTerminatingString(String terminatingString)
	{
		_terminatingStrings.add(terminatingString);
	}
	
	/**
	 * Return the string that caused the sequencer to stop.
	 * @return The string that caused the sequencer to stop.
	 */
	public String getTerminatedString()
	{
		return _terminatedString;
	}
	
	
	private boolean isTerminating(String string)
	{
		for (String term : _terminatingStrings)
		{
			if (term.equals(string))
			{
				_terminatedString = term;
				return true;
			}
		}
		return false;
	}

}
